<?php

namespace app\common\controller;

use GuzzleHttp\Exception\RequestException;
use think\Exception;
use think\Request;
use traits\think\Instance;
use WeChatPay\Formatter;
use WechatPay\GuzzleMiddleware\WechatPayMiddleware;
use WechatPay\GuzzleMiddleware\Util\PemUtil;
use GuzzleHttp\HandlerStack;


class WeChatPay
{
    use Instance;

    protected $data = [];
    protected $appid;
    protected $merchantId;// 商户号
    protected $merchantSerialNumber; // 商户API证书序列号
    protected $merchantPrivateKey; // 商户私钥文件路径
    protected $wechatpayCertificate; // 微信支付平台证书文件路径
    protected $notifyUrl; // 回调地址
    protected $client; // 调用客户端的

    /**
     * 初始化支付
     * */
    public function __construct($option = [])
    {
        $this->data = $option;
        $this->appid = config('site.appid');
        $this->merchantId = config('site.payConfig')['MerchantID']; // 商户号
        $this->notifyUrl = Request::instance()->domain() . '/client-api/v1/notify/weChatOrderNotify';
        // 「商户API证书」的「证书序列号」
        $this->merchantSerialNumber = config('site.payConfig')['certSn']; // 商户API证书序列号
        // 微信支付平台配置
        $this->merchantPrivateKey = PemUtil::loadPrivateKey('file://' . ROOT_PATH . 'public' . DS . 'cert' . DS . 'apiclient_key.pem'); // 商户私钥文件路径
        $this->wechatpayCertificate = PemUtil::loadCertificate('file://' . ROOT_PATH . 'public' . DS . 'cert' . DS . 'wechatpay.pem'); // 微信支付平台证书文件路径
        // 构造一个WechatPayMiddleware
        $wechatpayMiddleware = WechatPayMiddleware::builder()
            ->withMerchant($this->merchantId, $this->merchantSerialNumber, $this->merchantPrivateKey)// 传入商户相关配置
            ->withWechatPay([$this->wechatpayCertificate])// 可传入多个微信支付平台证书，参数类型为array
            ->build();
        // 将WechatPayMiddleware添加到Guzzle的HandlerStack中
        $stack = HandlerStack::create();
        $stack->push($wechatpayMiddleware, 'wechatpay');
        // 创建Guzzle HTTP Client时，将HandlerStack传入，接下来，正常使用Guzzle发起API请求，WechatPayMiddleware会自动地处理签名和验签
        $this->client = new \GuzzleHttp\Client(['handler' => $stack]);
    }

    /**
     * 微信支付
     * @return bool|mixed
     * @throws
     */
    public function weChatPay()
    {
        $params = json_decode($this->data['params'], true);
        try {
            $requestParam['json']['appid'] = $this->appid;
            $requestParam['json']['mchid'] = $this->merchantId;
            $requestParam['json']['description'] = '购买' . $params['name'];
            $requestParam['json']['out_trade_no'] = $this->data['order_sn'];
            $requestParam['json']['amount'] = ["total" => (int)bcmul($this->data['money'], 100), "currency" => "CNY"];
            $requestParam['json']['payer'] = ['openid' => $this->data['openid']];
            $requestParam['json']['notify_url'] = $this->notifyUrl;
            $requestParam['headers'] = ['Accept' => 'application/json'];
            $resp = $this->client->request('POST', 'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi', $requestParam);
            $statusCode = $resp->getStatusCode();
            if ($statusCode == 200) { //处理成功
                $order = json_decode($resp->getBody()->getContents(), true);
                $order['order_sn'] = $this->data['order_sn'];
                $order['payParam'] = $this->wxSign($order['prepay_id']);
                unset($order['prepay_id']);
                return $order;
            } else if ($statusCode == 204) { //处理成功，无返回Body
                return true;
            }
        } catch (RequestException $exception) {
            if ($exception->hasResponse()) {
                throw new Exception("failed,resp code = " . $exception->getResponse()->getStatusCode() . " return body = " . $exception->getResponse()->getBody() . "\n");
            }
            throw new Exception($exception->getMessage());
        }
    }

    /**
     * 进行微信签名
     * @param string $prepay_id
     * @return object|int|string|array|mixed|void
     * @throws
     */
    protected function wxSign(string $prepay_id)
    {
        $merchantPrivateKeyInstance = \WeChatPay\Crypto\Rsa::from($this->merchantPrivateKey);
        $params = [
            'appId' => $this->appid,
            'timeStamp' => (string)Formatter::timestamp(),
            'nonceStr' => Formatter::nonce(),
            'package' => 'prepay_id=' . $prepay_id,
        ];
        $params += ['paySign' => \WeChatPay\Crypto\Rsa::sign(
            Formatter::joinedByLineFeed(...array_values($params)),
            $merchantPrivateKeyInstance
        ), 'signType' => 'RSA'];

        return $params;
    }

    /**
     * 微信支付订单号查询订单
     * @throws
     */
    public function queryOrder()
    {
        try {
            if (empty($this->data['order_sn'])) {
                throw new Exception('缺少订单号');
            }
            $resp = $this->client->request(
                'GET',
                'https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/' . $this->data['order_sn'] . '?mchid=' . $this->merchantId,
                ['headers' => ['Accept' => 'application/json']]
            );
            $statusCode = $resp->getStatusCode();
            if ($statusCode == 200) { //处理成功
                $response = json_decode($resp->getBody()->getContents(), true);
                unset($response['appid'], $response['mchid']);
                if (!empty($response['amount']['total'])) {
                    $response['amount']['total'] = bcdiv($response['amount']['total'], 100, 2);
                }
                return $response;
            } else if ($statusCode == 204) { //处理成功，无返回Body
                return true;
            }
        } catch (RequestException  $exception) {
            if ($exception->hasResponse()) {
                throw new Exception("failed,resp code = " . $exception->getResponse()->getStatusCode() . " return body = " . $exception->getResponse()->getBody() . "\n");
            }
            throw new Exception($exception->getMessage());
        }
    }

    /**
     * 【服务端】关闭订单
     * @throws
     */
    public function closeOrder()
    {
        try {
            if (empty($this->data['order_sn'])) {
                throw new Exception('缺少订单号');
            }
            $resp = $this->client->request(
                'POST',
                'https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/' . $this->data['order_sn'] . '/close',
                ['json' => ['mchid' => $this->merchantId], 'headers' => ['Accept' => 'application/json']]
            );
            $statusCode = $resp->getStatusCode();
            if ($statusCode == 200) { //处理成功
                $response = $resp->getBody()->getContents();
                return $response;
            } else if ($statusCode == 204) { //处理成功，无返回Body
                return true;
            }
        } catch (RequestException  $exception) {
            if ($exception->hasResponse()) {
                throw new Exception("failed,resp code = " . $exception->getResponse()->getStatusCode() . " return body = " . $exception->getResponse()->getBody() . "\n");
            }
            throw new Exception($exception->getMessage());
        }
    }

    /**
     * 用户退款
     * @throws
     */
    public function refund()
    {
        try {
            $requestParams['json']['out_trade_no'] = $this->data['orders_sn'];
            $requestParams['json']['out_refund_no'] = $this->data['refund_order_sn'];
            $requestParams['json']['notify_url'] = Request::instance()->domain() . '/api/notify/refundNotify';
            $requestParams['json']['reason'] = !empty($this->data['note']) ? $this->data['note'] : '用户购买产品退款';
            $requestParams['json']['amount'] = ['refund' => (int)bcmul($this->data['refund_price'], 100), 'total' => (int)bcmul($this->data['price'], 100), 'currency' => 'CNY'];
            $requestParams['headers'] = ['Accept' => 'application/json'];
            $resp = $this->client->request(
                'POST',
                'https://api.mch.weixin.qq.com/v3/refund/domestic/refunds',
                $requestParams
            );
            $statusCode = $resp->getStatusCode();
            if ($statusCode == 200) { //处理成功
                $refund = json_decode($resp->getBody()->getContents(), JSON_UNESCAPED_UNICODE);
                $response = $refund;
                return $response;
            } else if ($statusCode == 204) { //处理成功，无返回Body
                return true;
            }
        } catch (RequestException $exception) {
            if ($exception->hasResponse()) {
                throw new Exception("failed,resp code = " . $exception->getResponse()->getStatusCode() . " return body = " . $exception->getResponse()->getBody() . "\n");
            }
            throw new Exception($exception->getMessage());
        }
    }
}